package cn.skyquant.quant4j.jforex.strategy;

import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseConfig;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Turtle extends BaseStrategy implements IBarFeedListener {

    static enum A{
        a1("普通止损"),
        a2("双重止损");
        String desc;
        A(String desc){
            this.desc = desc;
        }

        @Override
        public String toString() {
            return desc;
        }
    }
    @Configurable("交易品种")
    public Instrument instrument = Instrument.XAUUSD;
    @Configurable("入市:突破X日高点")
    public int _x = 55;
    @Configurable("退出:突破Y日低点")
    public int _y = 20;
    @Configurable("波动率选择")
    public int _n = 20;
    @Configurable("止损法选择")
    public A a =  A.a2;
    @Configurable("止损:Z倍波动率")
    public double _z = 2;
    @Configurable("仓位控制:最大亏损为总账户的百分比")
    public int _percent = 2;
    @Configurable("加仓次数")
    public int _add = 4;

    @Configurable("K柱开盘计算日")
    public boolean log_k = true;
    @Configurable("订单成交日志")
    public boolean log_fill = true;
    @Configurable("订单提交日志")
    public boolean log_submit = true;

    final Map<Instrument, Double> atr_map = new HashMap<>();
    final Map<Instrument, Double> min_low_x_map = new HashMap<>();
    final Map<Instrument, Double> max_high_x_map = new HashMap<>();
    final Map<Instrument, Double> min_low_y_map = new HashMap<>();
    final Map<Instrument, Double> max_high_y_map = new HashMap<>();
    TurtleConfig config;

    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体
    @Override
    protected void init(InitEntity initEntity) {
        config = new TurtleConfig(getName(), 0, 1, 0.2, instrument);
        initEntity.a.add(config);
        context.subscribeToBarsFeed(instrument, period, OfferSide.BID, this);
        initEntity.r = true;
        insMap.put(instrument, new InstrumentEntity(instrument));
    }

    @Override
    protected void onTickEx(Instrument instrument, ITick tick) {

    }

    @Override
    protected void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {

    }

    @Override
    protected void onMessageEx(IMessage message) {
        if (message.getOrder() == null) {
            return;
        }
        IOrder order = message.getOrder();
        OrderEx<TurtleOrderComment> orderEx = new OrderEx(order, new TurtleOrderComment());
        if (!orderEx.valid(getName())) {
            return;
        }
        Instrument instrument = order.getInstrument();
        InstrumentEntity ie = insMap.get(order.getInstrument());
        double amount = order.getAmount();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        double openprice = order.getOpenPrice();
        double firstprice = orderEx.orderComment.first_price;
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        List<IOrder> orderList = null;
        double atr = atr_map.get(instrument);
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            err("onMessageEx getOrders error:%s",e.getMessage());
            return ;
        }
        OrderExManager orderExManager = getOrderExManager(config, orderList);
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if(log_fill) notif("Turtle Fill, %s %s, open_price:%s,amount=%s,tp=%s,sl=%s", instrument.name(), orderCommand.name(), openprice, ie.formatAmout(amount), tp, sl);
                //每次成交一个单子，都要检查一下剩余哪些单子没挂
                if(order.getOrderCommand()== IEngine.OrderCommand.BUY){
                    //先检查现有的单子
                    Set<Integer> buySet = new HashSet<>();
                    Set<Integer> buyStopSet = new HashSet<>();
                    for(OrderEx buyOrder:orderExManager.buyList){
                        buySet.add(buyOrder.seriesIndex);
                    }
                    //再检查已经挂的单子
                    for(OrderEx buyStop:orderExManager.buyStopList){
                        buyStopSet.add(buyStop.seriesIndex);
                    }
                    for(int i=0;i<_add;i++){
                        if(buySet.contains(i) || buyStopSet.contains(i)){
                            continue;
                        }
                        //挂单
                        double buy_price =  new BigDecimal(firstprice + atr*_z/_add * i).setScale(ie.scale,RoundingMode.DOWN).doubleValue();
                        double sl_price_real =  _z * atr;
                        double sl_price = sl_price_real;
                        if(a==A.a2){
                            sl_price /= _add;
                        }
                        double sl_stop = new BigDecimal(buy_price - sl_price).setScale(ie.scale,RoundingMode.UP).doubleValue();
                        OrderEx orderEx_buy = new OrderEx(getName(), LabelType.N, 0, i, instrument, TradeDirection.LONG);
                        String label = orderEx_buy.toString();
                        TurtleOrderComment orderComment_buy = new TurtleOrderComment(0, i, OpenType.P, (int) account.getEquity(), context.getTime(), 0,firstprice);
                        String comment = orderComment_buy.toString();
                        try {
                            IOrder order_buy = engine.submitOrder(label, instrument, IEngine.OrderCommand.BUYSTOP, amount, buy_price, 0, sl_stop, 0, 0, comment);
                            order_buy.waitForUpdate(2000, IOrder.State.OPENED);
                        } catch (JFException e) {
                            err("subbmit order error:%s",e.getMessage());
                        }
                    }
                }else{
                    //先检查现有的单子
                    Set<Integer> sellSet = new HashSet<>();
                    Set<Integer> sellStopSet = new HashSet<>();
                    for(OrderEx sellOrder:orderExManager.sellList){
                        sellSet.add(sellOrder.seriesIndex);
                    }
                    //再检查已经挂的单子
                    for(OrderEx sellStop:orderExManager.sellStopList){
                        sellStopSet.add(sellStop.seriesIndex);
                    }
                    for(int i=0;i<_add;i++){
                        if(sellSet.contains(i) || sellStopSet.contains(i)){
                            continue;
                        }
                        //挂单
                        double sell_price = new BigDecimal(firstprice - atr*_z/_add * i).setScale(ie.scale,RoundingMode.DOWN).doubleValue();
                        double sl_price_real =  _z * atr;
                        double sl_price = sl_price_real;
                        if(a==A.a2){
                            sl_price /= _add;
                        }
                        double sl_stop = new BigDecimal(sell_price + sl_price).setScale(ie.scale,RoundingMode.UP).doubleValue();
//                        out("sell_price=%s,sl_price=%s",sell_price,sl_price);
                        OrderEx orderEx_sell = new OrderEx(getName(), LabelType.N, 0, i, instrument, TradeDirection.SHORT);
                        String label = orderEx_sell.toString();
                        TurtleOrderComment orderComment_sell = new TurtleOrderComment(0, i, OpenType.P, (int) account.getEquity(), context.getTime(), 0,firstprice);
                        String comment = orderComment_sell.toString();
                        try {
                            IOrder order_sell = engine.submitOrder(label, instrument, IEngine.OrderCommand.SELLSTOP, amount, sell_price, 0, sl_stop, 0, 0, comment);
                        } catch (JFException e) {
                            err("subbmit order error:%s",e.getMessage());
                        }
                    }
                }
                break;
            case ORDER_SUBMIT_OK:
                if (log_submit) {
                    out("Turtle Submit, label=%s,open_price=%s,tp=%s,sl=%s,create_time=%s,gtc=%s",
                            order.getLabel(),
                            order.getOpenPrice(),
                            order.getTakeProfitPrice(),
                            order.getStopLossPrice(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()),
                            CalendarUtils.formatGMTStandard(order.getGoodTillTime()));

                }
                break;
            case ORDER_CLOSE_OK:
                double closePrice = order.getClosePrice();
                double profit = order.getProfitLossInUSD();
                if (profit > 0) {
                    info("%s,Turtle %s %s Close,opentime=%s,fill_time=%s, open_price:%s,amount=%s,tp=%s,sl=%s,closePrice=%s,profit=%s,profit.pip=%s",
                            account.getBalance(), instrument.name(), orderCommand.name(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()), CalendarUtils.formatGMTStandard(order.getFillTime()),
                            openprice, ie.formatAmout(amount), tp, sl, closePrice, profit, order.getProfitLossInPips());
                } else if (profit < 0) {
                    err("%s,Turtle %s %s Close, opentime=%s,fill_time=%s, open_price:%s,amount=%s,tp=%s,sl=%s,closePrice=%s,profit=%s,profit.pip=%s",
                            account.getBalance(), instrument.name(), orderCommand.name(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()), CalendarUtils.formatGMTStandard(order.getFillTime()),
                            openprice, ie.formatAmout(amount), tp, sl, closePrice, profit, order.getProfitLossInPips());
                    if(orderCommand== IEngine.OrderCommand.BUY && a==A.a2){
                        if(!orderExManager.buyList.isEmpty()){
                            OrderEx orderEx_buy = new OrderEx(getName(), LabelType.N, 0, orderEx.seriesIndex, instrument, TradeDirection.LONG);
                            String label = orderEx_buy.toString();
                            TurtleOrderComment orderComment_buy = new TurtleOrderComment(0, orderEx.seriesIndex, OpenType.P, (int) account.getEquity(), context.getTime(), 0,firstprice);
                            String comment = orderComment_buy.toString();
                            try {
                                IOrder order_buy = engine.submitOrder(label, instrument, IEngine.OrderCommand.BUYSTOP, amount, order.getOpenPrice(), 0, order.getStopLossPrice(), 0, 0, comment);
                            } catch (JFException e) {
                                err("submit order error:%s",e.getMessage());
                            }
                        }
                    }else {
                        if(!orderExManager.sellList.isEmpty() && a==A.a2){
                            OrderEx orderEx_sell = new OrderEx(getName(), LabelType.N, 0, orderEx.seriesIndex, instrument, TradeDirection.SHORT);
                            String label = orderEx_sell.toString();
                            TurtleOrderComment orderComment_sell = new TurtleOrderComment(0, orderEx.seriesIndex, OpenType.P, (int) account.getEquity(), context.getTime(), 0,firstprice);
                            String comment = orderComment_sell.toString();
                            try {
                                IOrder order_buy = engine.submitOrder(label, instrument, IEngine.OrderCommand.SELLSTOP, amount, order.getOpenPrice(), 0, order.getStopLossPrice(), 0, 0, comment);
                            } catch (JFException e) {
                                err("submit order error:%s",e.getMessage());
                            }
                        }
                    }
                } else {
//                    if (log_submit)
//                        warn("%s,Turtle %s %s Cancel, opentime=%s,open_price:%s,%s,profit=%s", account.getBalance(), instrument.name(), orderCommand.name(), CalendarUtils.formatGMTStandard(order.getCreationTime()), order.getOpenPrice(), message, profit);
                }
                break;
        }
    }

    @Override
    protected void onAccountEx(IAccount account) {

    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        ITimeDomain timeDomain0;
        ITimeDomain timeDomain1;
        try {
            timeDomain0 = dataService.getOfflineTimeDomain(0);
            timeDomain1 = dataService.getOfflineTimeDomain(-1);
        } catch (JFException e) {
            if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
            return;
        }
        if (timeDomain0 == null || timeDomain1 == null) {
            if (log_error) warn("onBar.getOffline,TimeDomain is null");
            return;
        }
        //停盘时间不干活
        if (timeDomain1.getEnd() > context.getTime() || context.getTime() > timeDomain0.getStart()) {
            return;
        }
        long barstart = 0;
        long barnextstart = 0;
        try {
            barstart = history.getBarStart(period, context.getTime());
            barnextstart = history.getNextBarStart(period, context.getTime());
        } catch (JFException e) {
            return;
        }
        if (!calc(instrument, barstart, barnextstart)) {
            return;
        }
        List<IOrder> orderList = null;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            return;
        }
        processByConfig(instrument, config, orderList, bar, barstart, barnextstart);
    }

    private void processByConfig(Instrument instrument, TurtleConfig config, List<IOrder> orderList, IBar bar, long barstart, long barnextstart) {

        OrderExManager orderExManager = getOrderExManager(config, orderList);
        //对于既有的单子做止损修改
        sl_change(instrument, orderExManager, barstart, bar);
        //入场
        in(instrument, orderExManager, bar, barstart, barnextstart);
    }


    private void in(Instrument instrument, OrderExManager orderExManager, IBar bar, long barstart, long barnextstart) {
        //先看现在有的单子
        double min_low_x = min_low_x_map.get(instrument);
        double max_high_x = max_high_x_map.get(instrument);
        double atr = atr_map.get(instrument);
        InstrumentEntity ie = insMap.get(instrument);

        if(orderExManager.buyList.isEmpty() && orderExManager.buyStopList.isEmpty()){
            double buy_price = max_high_x;
            double sl_price_real =  _z * atr;
            double sl_price = sl_price_real;
            if(a==A.a2){
                sl_price /= _add;
            }
            double sl = new BigDecimal(buy_price - sl_price).setScale(ie.scale, RoundingMode.UP).doubleValue();
            double total_sl_money = account.getEquity() * _percent / 100;
            double total_amount = total_sl_money / ie.minMoney / (sl_price_real * ie.pipMulti) * ie.minAmount;//总仓位
//            out("total_amount=%s,total_sl_money=%s,minMoney=%s,sl_price_real=%s,pipMulti=%s,minAmount=%s",total_amount,total_sl_money,ie.minMoney,sl_price_real,ie.pipMulti,ie.minAmount);
            double amount = total_amount / _add;//单次开仓仓位
            if(amount<ie.minAmount){
                amount = ie.minAmount;
            }
            OrderEx orderEx_buy = new OrderEx(getName(), LabelType.N, 0, 0, instrument, TradeDirection.LONG);
            String label = orderEx_buy.toString();
            TurtleOrderComment orderComment_buy = new TurtleOrderComment(0, 0, OpenType.P, (int) account.getEquity(), context.getTime(), 0,buy_price);
            String comment = orderComment_buy.toString();
            try {
                IOrder order_buy = engine.submitOrder(label, instrument, IEngine.OrderCommand.BUYSTOP, amount, buy_price, 0, sl, 0, barnextstart - 60000, comment);
            } catch (JFException e) {
                err("subbmit order error:%s",e.getMessage());
            }

        }
        if(orderExManager.sellList.isEmpty() && orderExManager.sellStopList.isEmpty()){
            double sell_price = min_low_x;
            double sl_price_real =  _z * atr;
            double sl_price = sl_price_real;
            if(a==A.a2){
                sl_price /= _add;
            }
            double sl = new BigDecimal(sell_price + sl_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
            double total_sl_money = account.getEquity() * _percent / 100;
            double total_amount = total_sl_money / ie.minMoney / (sl_price_real * ie.pipMulti)* ie.minAmount;//总仓位
            double amount = total_amount / _add;//单次开仓仓位
            if(amount<ie.minAmount){
                amount = ie.minAmount;
            }
            OrderEx orderEx_sell = new OrderEx(getName(), LabelType.N, 0, 0, instrument, TradeDirection.SHORT);
            String label = orderEx_sell.toString();
            TurtleOrderComment orderComment_sell = new TurtleOrderComment(0, 0, OpenType.P, (int) account.getEquity(), context.getTime(), 0,sell_price);
            String comment = orderComment_sell.toString();
            try {
                IOrder order_sell = engine.submitOrder(label, instrument, IEngine.OrderCommand.SELLSTOP, amount, sell_price, 0, sl, 0, barnextstart - 60000, comment);
            } catch (JFException e) {
                err("subbmit order error:%s",e.getMessage());
            }

        }
    }


    private void sl_change(Instrument instrument, OrderExManager orderExManager, long barstart, IBar bar) {
        //止损就是_y日高低点
        InstrumentEntity ie = insMap.get(instrument);
        double min_low_y = min_low_y_map.get(instrument);
        double max_high_y = max_high_y_map.get(instrument);
        double atr = atr_map.get(instrument);

        for (OrderEx orderEx : orderExManager.buyList) {
            if (orderEx == null || orderEx.order == null) {
                continue;
            }
            try{
                IOrder order = orderEx.order;
                double old_sl = orderEx.order.getStopLossPrice();
                if(min_low_y>old_sl){
                    order.setStopLossPrice(min_low_y);
                }

            }catch (Throwable e){
                err("setStopLossPrice error:%s",e.getMessage());
            }
        }

        for (OrderEx orderEx : orderExManager.sellList) {
            if (orderEx == null || orderEx.order == null) {
                continue;
            }
            try{
                IOrder order = orderEx.order;
                double old_sl = orderEx.order.getStopLossPrice();
                if(max_high_y < old_sl){
                    order.setStopLossPrice(max_high_y);
                }
            }catch (Throwable e){
                err("setStopLossPrice error:%s",e.getMessage());
            }
        }

        //取消所有比55日高点还高的挂多单
        for(OrderEx buyStop:orderExManager.buyStopList){
            if(buyStop.order.getOpenPrice() > max_high_y+atr){
                try {
                    buyStop.order.close();
                } catch (JFException e) {
                    err("cancel order error:%s",e.getMessage());
                }
            }
        }

        //取消所有比55日低点还低的挂空单
        for(OrderEx sellStop:orderExManager.sellStopList){
            if(sellStop.order.getOpenPrice() < min_low_y-atr){
                try {
                    sellStop.order.close();
                } catch (JFException e) {
                    err("cancel order error:%s",e.getMessage());
                }
            }
        }
    }


    //先计算关键指标
    private boolean calc(Instrument instrument, long barstart, long barnextstartt) {
        double[] atrs = null;
        double min_low_x = -1;
        double max_high_x = 100000;
        double min_low_y = -1;
        double max_high_y = 100000;

        try {
            atrs = indicators.atr(instrument, period, OfferSide.ASK, _n, Filter.WEEKENDS, 1, barstart, 0);
            min_low_x = indicators.min(instrument, period, OfferSide.BID, IIndicators.AppliedPrice.LOW, _x, 0);
            max_high_x = indicators.max(instrument, period, OfferSide.ASK, IIndicators.AppliedPrice.HIGH, _x, 0);
            min_low_y = indicators.min(instrument, period, OfferSide.BID, IIndicators.AppliedPrice.LOW, _y, 0);
            max_high_y = indicators.max(instrument, period, OfferSide.ASK, IIndicators.AppliedPrice.HIGH, _y, 0);
        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
            return false;
        }
        if (atrs == null || atrs.length == 0
                || min_low_x <= 0 || max_high_x >= 99999
                || min_low_y <= 0 || max_high_y >= 99999) {
            return false;
        }
        double atr = atrs[0];

        if (log_k) {
            InstrumentEntity ie = insMap.get(instrument);
            out("%s,atr[%d]=%s,min[%d]=%s,max[%d]=%s,min[%d]=%s,max[%d]=%s",
                    period == Period.DAILY ? CalendarUtils.formatGMTSimple(barstart) : CalendarUtils.formatGMTStandard(barstart),
                    _n, ie.formatPrice(atr),
                    _x, min_low_x,
                    _x, max_high_x,
                    _y, min_low_y,
                    _y, max_high_y);
        }
        atr_map.put(instrument, atr);
        min_low_x_map.put(instrument, min_low_x);
        max_high_x_map.put(instrument, max_high_x);
        min_low_y_map.put(instrument, min_low_y);
        max_high_y_map.put(instrument, max_high_y);
        return true;
    }

    @Override
    protected String getVersion() {
        return "010";
    }

    @Override
    protected String getName() {
        return "Turtle"+eaId;
    }

    @Override
    protected String getEaName() {
        return "Turtle";
    }

    private OrderExManager getOrderExManager(TurtleConfig config, List<IOrder> orderList) {

        final OrderExManager o = new OrderExManager();
        for (IOrder order : orderList) {
            TurtleOrderComment orderComment = new TurtleOrderComment();
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(config.name, config.index) || !orderEx.orderComment.valid) {
                continue;
            }
            if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getState() == IOrder.State.FILLED) {
                o.buyList.add(orderEx);
                o.buy_pl_usd += order.getProfitLossInUSD();
                o.buy_pl_pip += order.getProfitLossInPips();
                o.buy_c += order.getCommissionInUSD();
                if (o.buy_first == null || orderEx.seriesIndex < o.buy_first.seriesIndex) {
                    o.buy_first = orderEx;
                }
                if (o.buy_last == null || orderEx.seriesIndex > o.buy_last.seriesIndex) {
                    o.buy_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getState() == IOrder.State.FILLED) {
                o.sellList.add(orderEx);
                o.sell_pl_usd += order.getProfitLossInUSD();
                o.sell_pl_pip += order.getProfitLossInPips();
                o.sell_c += order.getCommissionInUSD();
                if (o.sell_first == null || orderEx.seriesIndex < o.sell_first.seriesIndex) {
                    o.sell_first = orderEx;
                }
                if (o.sell_last == null || orderEx.seriesIndex > o.sell_last.seriesIndex) {
                    o.sell_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT && order.getState() == IOrder.State.OPENED) {
                o.sellLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT && order.getState() == IOrder.State.OPENED) {
                o.buyLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLSTOP && order.getState() == IOrder.State.OPENED) {
                o.sellStopList.add(orderEx);
            }
        }
        return o;
    }

    final static class TurtleConfig extends BaseConfig {
        final Instrument instrument;//货币

        TurtleConfig(String name, int index, double m, double lossLimit, Instrument instrument) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
        }

        @Override
        public String toString() {
            return "TurtleConfig" + index
                    + "_" + instrument.name()
                    + "_m_" + m;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m > 0;
        }
    }

    final static class TurtleOrderComment extends BaseOrderComment {
        public double first_price;
        public TurtleOrderComment() {
        }

        TurtleOrderComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss,double first_price) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
            this.first_price = first_price;
        }

        @Override
        public void parse(String comment) {
            super.parse(comment);
            if (s.length >= getParamNum()+1) {
                try {
                    first_price = Double.parseDouble(s[6]);
                    valid = true;
                } catch (Exception e) {
                    valid = false;
                }
            }
        }

        @Override
        public String toString() {
            return super.toString()+"_"+first_price;
        }
    }
}
